Skip to content

Conversation

@mushrowan
Copy link

summary

adds systemd.maskedUnits option to mask units shipped by the host distro (e.g.
ssh.service on ubuntu). masked units are symlinked to /dev/null, preventing
them from starting manually or as a dependency.

closes #306

usage

{
  systemd.maskedUnits = [
    "ssh.service"
    "ModemManager.service"
  ];
}

changes

  • new systemd.maskedUnits list option in systemd.nix
  • masked units included in services.json with masked: true
  • assertion prevents overlap between defined units and masked units
  • ServiceConfig.store_path is now Option<StorePath> for masked units
  • activation stops masked services, skips them for reload/restart
  • container test covers mask lifecycle

design note

went with a top-level list rather than per-service
systemd.services.<name>.mask = true — the use case is masking units you don't
define yourself. for units you define, enable = false already works.

@mushrowan
Copy link
Author

wow, my markdown formatter really garbled this one in the pr view. i can edit if it's hard to read!

Copy link
Member

@jfroche jfroche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for taking the time to create this feature !

At first glance I see:

  • we could simplify the test by masking an existing service started in the container like unattended-upgrades.service
  • masking a unit doesn't stop the service. Should we mask the service before reloading systemd or should we stop the service explicitly ?
  • system-manager deactivation should unmask the masked services

@mushrowan mushrowan force-pushed the mushrowan/306 branch 2 times, most recently from 8056787 to f2ea34c Compare February 11, 2026 21:18
@mushrowan
Copy link
Author

Thank you for taking the time to create this feature !

At first glance I see:

* we could simplify the test by masking an existing service started in the container like `unattended-upgrades.service`

* masking a unit doesn't stop the service. Should we mask the service before reloading systemd or should we stop the service explicitly ?

* system-manager deactivation should unmask the masked services

hopefully addressed all of these in the latest push - using dbus for masking, and unattended-upgrades for testing. masked services are now added to the stop list before the mask. thank you for reviewing!

@jfroche
Copy link
Member

jfroche commented Feb 13, 2026

All good, last nitpick before we merge:

in the test we should check that the service is running before activation and stopped after activation:

+        with subtest("Service can be started before activation"):
+            assert machine.service("unattended-upgrades").is_running, "unattended-upgrades should be running before activation"

        machine.activate()
        machine.wait_for_unit("system-manager.target")

+        with subtest("Masked service is not running"):
+            assert not machine.service("unattended-upgrades").is_running, "unattended-upgrades should not be running"

symlink units to /dev/null to prevent them from starting, useful for
disabling distro-shipped units like ssh.service or
unattended-upgrades.service

- nix option with assertion preventing overlap with defined units
- masked entries in services.json with storePath null
- mask/unmask via D-Bus MaskUnitFiles/UnmaskUnitFiles
- running services are explicitly stopped when masked
- deactivation unmasks via D-Bus before daemon-reload
- container test masks unattended-upgrades.service and verifies
  masking, start prevention, and unmask on deactivation

closes numtide#306
@mushrowan
Copy link
Author

All good, last nitpick before we merge:

in the test we should check that the service is running before activation and stopped after activation:

+        with subtest("Service can be started before activation"):
+            assert machine.service("unattended-upgrades").is_running, "unattended-upgrades should be running before activation"

        machine.activate()
        machine.wait_for_unit("system-manager.target")

+        with subtest("Masked service is not running"):
+            assert not machine.service("unattended-upgrades").is_running, "unattended-upgrades should not be running"

edited, thanks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow to mask systemd services

2 participants